package com.flmz.judge.service.impl;

import cn.hutool.core.bean.BeanUtil;
import com.alibaba.fastjson2.JSON;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.flmz.api.domain.UserExeResult;
import com.flmz.api.domain.dto.JudgeSubmitDTO;
import com.flmz.api.domain.vo.UserQuestionResultVO;
import com.flmz.common.core.constants.Constants;
import com.flmz.common.core.constants.JudgeConstants;
import com.flmz.common.core.enums.CodeRunStatus;
import com.flmz.judge.domain.SandBoxExecuteResult;
import com.flmz.judge.domain.UserSubmit;
import com.flmz.judge.mapper.UserSubmitMapper;
import com.flmz.judge.service.IJudgeService;
import com.flmz.judge.service.ISandboxPoolService;
import com.flmz.judge.service.ISandboxService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.List;

@Service
@Slf4j
public class JudgeServiceImpl implements IJudgeService {

	@Autowired
	private ISandboxService sandboxService;

	@Autowired
	private ISandboxPoolService sandboxPoolService;

	@Autowired
	private UserSubmitMapper userSubmitMapper;

	@Override
	public UserQuestionResultVO doJudgeJavaCode(JudgeSubmitDTO judgeSubmitDTO) {
		log.info("---- 判题逻辑开始 -------");
		SandBoxExecuteResult sandBoxExecuteResult =
				sandboxPoolService.exeJavaCode(judgeSubmitDTO.getUserId(), judgeSubmitDTO.getUserCode(), judgeSubmitDTO.getInputList());
		UserQuestionResultVO userQuestionResultVO = new UserQuestionResultVO();
		if (sandBoxExecuteResult != null && CodeRunStatus.SUCCEED.equals(sandBoxExecuteResult.getRunStatus())) {
			// 执行成功，继续详细比对执行结果，时间限制，空间限制
			userQuestionResultVO = doJudge(judgeSubmitDTO, sandBoxExecuteResult, userQuestionResultVO);
		} else {
			// 执行失败
			userQuestionResultVO.setPass(Constants.FALSE);
			if (sandBoxExecuteResult != null) {
				userQuestionResultVO.setExeMessage(sandBoxExecuteResult.getExeMessage());
			} else {
				userQuestionResultVO.setExeMessage(CodeRunStatus.UNKNOWN_FAILED.getMsg());
			}
			userQuestionResultVO.setScore(JudgeConstants.ERROR_SCORE);
		}
		saveUserSubmit(judgeSubmitDTO, userQuestionResultVO);
		log.info("判题逻辑结束，判题结果为： {} ", userQuestionResultVO);
		return userQuestionResultVO;
	}

	private UserQuestionResultVO doJudge(JudgeSubmitDTO judgeSubmitDTO,
										 SandBoxExecuteResult sandBoxExecuteResult,
										 UserQuestionResultVO userQuestionResultVO) {
		// 比较沙箱执行输出的数量和预期输出的数量
		List<String> exeOutputList = sandBoxExecuteResult.getOutputList();
		List<String> outputList = judgeSubmitDTO.getOutputList();
		if (outputList.size() != exeOutputList.size()) {
			userQuestionResultVO.setScore(JudgeConstants.ERROR_SCORE);
			userQuestionResultVO.setPass(Constants.FALSE);
			userQuestionResultVO.setExeMessage(CodeRunStatus.NOT_ALL_PASSED.getMsg());
			return userQuestionResultVO;
		}
		List<UserExeResult> userExeResultList = new ArrayList<>();
		boolean passed = resultCompare(judgeSubmitDTO, exeOutputList, outputList, userExeResultList);
		return assembleUserQuestionResultVO(judgeSubmitDTO, sandBoxExecuteResult, userQuestionResultVO, userExeResultList, passed);
	}

	// 组装最终的判题结果
	private UserQuestionResultVO assembleUserQuestionResultVO(JudgeSubmitDTO judgeSubmitDTO,
															  SandBoxExecuteResult sandBoxExecuteResult,
															  UserQuestionResultVO userQuestionResultVO,
															  List<UserExeResult> userExeResultList, boolean passed) {
		userQuestionResultVO.setUserExeResultList(userExeResultList);
		if (!passed) {
			userQuestionResultVO.setPass(Constants.FALSE);
			userQuestionResultVO.setScore(JudgeConstants.ERROR_SCORE);
			userQuestionResultVO.setExeMessage(CodeRunStatus.NOT_ALL_PASSED.getMsg());
			return userQuestionResultVO;
		}
		if (sandBoxExecuteResult.getUseMemory() > judgeSubmitDTO.getSpaceLimit()) {
			userQuestionResultVO.setPass(Constants.FALSE);
			userQuestionResultVO.setScore(JudgeConstants.ERROR_SCORE);
			userQuestionResultVO.setExeMessage(CodeRunStatus.OUT_OF_MEMORY.getMsg());
			return userQuestionResultVO;
		}
		if (sandBoxExecuteResult.getUseTime() > judgeSubmitDTO.getTimeLimit()) {
			userQuestionResultVO.setPass(Constants.FALSE);
			userQuestionResultVO.setScore(JudgeConstants.ERROR_SCORE);
			userQuestionResultVO.setExeMessage(CodeRunStatus.OUT_OF_TIME.getMsg());
			return userQuestionResultVO;
		}
		userQuestionResultVO.setPass(Constants.TRUE);
		int score = judgeSubmitDTO.getDifficulty() * JudgeConstants.DEFAULT_SCORE;
		userQuestionResultVO.setScore(score);
		return userQuestionResultVO;
	}

	// 逐个比对每个测试用例的实际输出和预期输出
	private boolean resultCompare(JudgeSubmitDTO judgeSubmitDTO, List<String> exeOutputList,
								  List<String> outputList, List<UserExeResult> userExeResultList) {
		boolean passed = true;
		for (int index = 0; index < outputList.size(); index++) {
			String output = outputList.get(index);
			String exeOutPut = exeOutputList.get(index);
			String input = judgeSubmitDTO.getInputList().get(index);
			UserExeResult userExeResult = new UserExeResult();
			userExeResult.setInput(input);
			userExeResult.setOutput(output);
			userExeResult.setExeOutput(exeOutPut);
			userExeResultList.add(userExeResult);
			if (!output.equals(exeOutPut)) {
				passed = false;
				log.info("输入：{}， 期望输出：{}， 实际输出：{} ", input, output, exeOutputList);
			}
		}
		return passed;
	}

	// 将用户提交和判题结果保存到数据库
	private void saveUserSubmit(JudgeSubmitDTO judgeSubmitDTO, UserQuestionResultVO userQuestionResultVO) {
		UserSubmit userSubmit = new UserSubmit();
		BeanUtil.copyProperties(userQuestionResultVO, userSubmit);
		userSubmit.setUserId(judgeSubmitDTO.getUserId());
		userSubmit.setQuestionId(judgeSubmitDTO.getQuestionId());
		userSubmit.setExamId(judgeSubmitDTO.getExamId());
		userSubmit.setProgramType(judgeSubmitDTO.getProgramType());
		userSubmit.setUserCode(judgeSubmitDTO.getUserCode());
		userSubmit.setCaseJudgeRes(JSON.toJSONString(userQuestionResultVO.getUserExeResultList()));
		userSubmit.setCreateBy(judgeSubmitDTO.getUserId());
		userSubmitMapper.delete(new LambdaQueryWrapper<UserSubmit>()
				.eq(UserSubmit::getUserId, judgeSubmitDTO.getUserId())
				.eq(UserSubmit::getQuestionId, judgeSubmitDTO.getQuestionId())
				.isNull(judgeSubmitDTO.getExamId() == null, UserSubmit::getExamId)
				.eq(judgeSubmitDTO.getExamId() != null, UserSubmit::getExamId, judgeSubmitDTO.getExamId()));
		userSubmitMapper.insert(userSubmit);
	}
}
